﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Tww.Wpf.Controls.Controls.Shared.Enums;
using Tww.Wpf.Controls.Controls.Shared.Helper;
using Tww.Wpf.Controls.Controls.Windows;

namespace Tww.Wpf.Controls.Controls.Flyouts
{
    /// <summary>
    /// A FlyoutsControl is for displaying flyouts in a MetroWindow.
    /// <see cref="MetroWindow"/>
    /// </summary>
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof (Flyout))]
    public class FlyoutContainer : ItemsControl
    {
        #region  - Felder (öffentlich) -

        public static readonly DependencyProperty OverrideExternalCloseButtonProperty =
            DependencyProperty.Register("OverrideExternalCloseButton", typeof (MouseButton?), typeof (FlyoutContainer),
                new PropertyMetadata(null));

        public static readonly DependencyProperty OverrideIsPinnedProperty =
            DependencyProperty.Register("OverrideIsPinned", typeof (bool), typeof (FlyoutContainer),
                new PropertyMetadata(false));

        #endregion

        #region  - Konstruktoren -

        static FlyoutContainer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof (FlyoutContainer), new FrameworkPropertyMetadata(typeof (FlyoutContainer)));
        }

        #endregion

        #region  - Indexer und Eigenschaften (öffentlich) -

        /// <summary>
        /// Gets/sets whether <see cref="MahApps.Metro.Controls.Flyout.ExternalCloseButton"/> is ignored and all flyouts behave as if it was set to the value of this property.
        /// </summary>
        public MouseButton? OverrideExternalCloseButton
        {
            get { return (MouseButton?) GetValue(OverrideExternalCloseButtonProperty); }
            set { SetValue(OverrideExternalCloseButtonProperty, value); }
        }

        /// <summary>
        /// Gets/sets whether <see cref="MahApps.Metro.Controls.Flyout.IsPinned"/> is ignored and all flyouts behave as if it was set false.
        /// </summary>
        public bool OverrideIsPinned
        {
            get { return (bool) GetValue(OverrideIsPinnedProperty); }
            set { SetValue(OverrideIsPinnedProperty, value); }
        }

        #endregion

        #region  - Methoden (privat) -

        internal IEnumerable<Flyout> GetFlyouts()
        {
            return GetFlyouts(Items);
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new Flyout();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is Flyout;
        }

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AttachHandlers(GetFlyouts(e.NewItems));
                    break;
                case NotifyCollectionChangedAction.Replace:
                    AttachHandlers(GetFlyouts(e.NewItems));
                    DetachHandlers(GetFlyouts(e.OldItems));
                    break;
                case NotifyCollectionChangedAction.Remove:
                    DetachHandlers(GetFlyouts(e.OldItems));
                    break;
                case NotifyCollectionChangedAction.Reset:
                    List<Flyout> flyouts = GetFlyouts(Items).ToList();
                    DetachHandlers(flyouts);
                    AttachHandlers(flyouts);
                    break;
            }
        }

        private void AttachHandlers(IEnumerable<Flyout> items)
        {
            foreach (Flyout item in items)
            {
                AttachHandlers(item);
            }
        }

        private void AttachHandlers(Flyout item)
        {
            DependencyPropertyDescriptor isOpenChanged = DependencyPropertyDescriptor.FromProperty(Flyout.ElementSizeProperty, typeof(Flyout));
            isOpenChanged.AddValueChanged(item, FlyoutStatusChanged);
        }

        private void DetachHandlers(IEnumerable<Flyout> items)
        {
            foreach (Flyout item in items)
            {
                DetachHandlers(item);
            }
        }

        private void DetachHandlers(Flyout item)
        {
            DependencyPropertyDescriptor FlyoutSizeChanged = DependencyPropertyDescriptor.FromProperty(Flyout.ElementSizeProperty, typeof(Flyout));
            FlyoutSizeChanged.RemoveValueChanged(item, FlyoutStatusChanged);
        }

        private void FlyoutStatusChanged(object sender, EventArgs e)
        {
            Flyout flyout = GetInvokedFlyout(sender);

            ReorderZIndices(flyout);

            var parentWindow = this.TryFindParent<MetroWindow>();
            if (parentWindow != null)
            {
                IOrderedEnumerable<Flyout> visibleFlyouts =
                    GetFlyouts(Items).Where(i => i.ElementSize != ElementSize.Collapsed).OrderBy(Panel.GetZIndex);
                parentWindow.HandleFlyoutStatusChange(flyout, visibleFlyouts);
            }
        }

        private Flyout GetInvokedFlyout(object item)
        {
            var flyout = item as Flyout;
            if (flyout != null)
            {
                return flyout;
            }

            return (Flyout) ItemContainerGenerator.ContainerFromItem(item);
        }

        private IEnumerable<Flyout> GetFlyouts(IEnumerable items)
        {
            return items.OfType<object>().Select(GetInvokedFlyout);
        }

        private void ReorderZIndices(Flyout lastChanged)
        {
            IOrderedEnumerable<Flyout> visibleFlyouts =
                GetFlyouts(Items).Where(i => i.ElementSize != ElementSize.Collapsed && i != lastChanged).OrderBy(Panel.GetZIndex);
            int index = 0;
            foreach (Flyout openFlyout in visibleFlyouts)
            {
                Panel.SetZIndex(openFlyout, index);
                index++;
            }

            if (lastChanged.ElementSize != ElementSize.Collapsed)
            {
                Panel.SetZIndex(lastChanged, index);
            }
        }

        #endregion
    }
}